package com.idanchuang.support.etcache.remote;

import com.idanchuang.support.etcache.EtCacheProperties;
import com.idanchuang.support.etcache.exception.RemoteCacheException;
import com.idanchuang.support.etcache.remote.codec.ValueCodec;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * RemoteCache 的抽象实现
 *
 * @author yjy
 * Created at 2021/12/21 11:10 上午
 */
public abstract class AbstractRemoteCache<K, V> implements RemoteCache<K, V> {

    /**
     * 远程缓存配置
     */
    protected RemoteCacheConfig config;
    protected EtCacheProperties properties;

    public AbstractRemoteCache(RemoteCacheConfig config) {
        this.config = config;
        this.properties = config.getProperties();
    }

    /**
     * 根据原始Key构建 RemoteKey 并编码
     * @param key 原始key
     * @return 编码后的RemoteKey
     */
    protected byte[] buildKey(K key) {
        byte[] keyBytes = config.getKeyCodec().encode(key);
        int pressure = config.getPressureChecker().isPressure() ? 1 : 0;
        RemoteKey remoteKey = new RemoteKey(config.getCacheName(), pressure, keyBytes);
        return this.config.getRemoteKeyCodec().encode(remoteKey);
    }

    @Override
    public V get(K key) {
        try {
            byte[] newKey = buildKey(key);
            byte[] valueBytes = doGet(newKey);
            if (valueBytes == null) {
                return null;
            }
            return (V) config.getValueCodec().decode(valueBytes);
        } catch (Exception e) {
            throw new RemoteCacheException(e);
        }
    }

    /**
     * 读取缓存
     * @param keyBytes ..
     * @return ..
     */
    protected abstract byte[] doGet(byte[] keyBytes);

    @Override
    public Map<K, V> getAll(Iterable<K> keys) {
        Map<K, V> resultMap = new HashMap<>();
        for (K k : keys) {
            resultMap.put(k, get(k));
        }
        return resultMap;
    }

    /**
     * 批量读取缓存
     * @param keyBytesList ..
     * @return ..
     */
    protected abstract Map<byte[], byte[]> doGetAll(List<byte[]> keyBytesList);

    @Override
    public void set(K key, V value) {
        try {
            byte[] keyBytes = buildKey(key);
            byte[] valueBytes = config.getValueCodec().encode(value);
            if (properties.getMaxExpire() > 0) {
                doSet(keyBytes, valueBytes, properties.getMaxExpire(), properties.getExpireUnit());
            } else {
                doSet(keyBytes, valueBytes);
            }
            // publish event
            publishEvent(keyBytes);
        } catch (Exception e) {
            throw new RemoteCacheException(e);
        }
    }

    /**
     * 设置缓存
     * @param key ..
     * @param value ..
     */
    protected abstract void doSet(byte[] key, byte[] value);

    @Override
    public void set(K key, V value, long expire, TimeUnit timeUnit) {
        try {
            byte[] keyBytes = buildKey(key);
            byte[] valueBytes = config.getValueCodec().encode(value);
            doSet(keyBytes, valueBytes, expire, timeUnit);
            // publish event
            publishEvent(keyBytes);
        } catch (Exception e) {
            throw new RemoteCacheException(e);
        }
    }

    /**
     * 设置缓存, 并设置过期时间
     * @param keyBytes ..
     * @param valueBytes ..
     * @param expire 过期时间
     * @param timeUnit 时间单位
     */
    protected abstract void doSet(byte[] keyBytes, byte[] valueBytes, long expire, TimeUnit timeUnit);

    @Override
    public void setAll(Map<K, V> map) {
        try {
            Map<byte[], byte[]> data = new HashMap<>();
            map.forEach((k, v) -> data.put(buildKey(k), config.getValueCodec().encode(v)));
            if (properties.getMaxExpire() > 0) {
                doSetAll(data, properties.getMaxExpire(), properties.getExpireUnit());
            } else {
                doSetAll(data);
            }

            // publish event
            data.keySet().forEach(this::publishEvent);
        } catch (Exception e) {
            throw new RemoteCacheException(e);
        }
    }

    /**
     * 批量设置缓存
     * @param map 键值对集合
     */
    protected abstract void doSetAll(Map<byte[], byte[]> map);

    @Override
    public void setAll(Map<K, V> map, long expire, TimeUnit timeUnit) {
        try {
            Map<byte[], byte[]> data = new HashMap<>();
            map.forEach((k, v) -> data.put(buildKey(k), config.getValueCodec().encode(v)));
            doSetAll(data, expire, timeUnit);
            // publish event
            data.keySet().forEach(this::publishEvent);
        } catch (Exception e) {
            throw new RemoteCacheException(e);
        }
    }

    /**
     * 批量设置缓存, 并设置过期时间
     * @param map 键值对集合
     * @param expire 过期时间
     * @param timeUnit 时间单位
     */
    protected abstract void doSetAll(Map<byte[], byte[]> map, long expire, TimeUnit timeUnit);

    @Override
    public void remove(K key) {
        try {
            byte[] keyBytes = buildKey(key);
            doRemove(keyBytes);
            // publish event
            publishEvent(keyBytes);
        } catch (Exception e) {
            throw new RemoteCacheException(e);
        }
    }

    /**
     * 移除一个缓存
     * @param keyBytes ..
     */
    protected abstract void doRemove(byte[] keyBytes);

    @Override
    public void removeAll(Iterable<K> keys) {
        try {
            List<byte[]> keyBytesList = new ArrayList<>();
            for (K k : keys) {
                keyBytesList.add(buildKey(k));
            }
            doRemoveAll(keyBytesList);
            // publish event
            keyBytesList.forEach(this::publishEvent);
        } catch (Exception e) {
            throw new RemoteCacheException(e);
        }
    }

    /**
     * 批量移除缓存
     * @param keyBytesList ..
     */
    protected abstract void doRemoveAll(List<byte[]> keyBytesList);

    /**
     * 广播缓存变更事件
     * @param remoteKeyBytes 缓存key
     */
    private void publishEvent(byte[] remoteKeyBytes) {
        config.getPublisher().publish(remoteKeyBytes);
    }

    @Override
    public ValueCodec getKeyCodec() {
        return this.config.keyCodec;
    }

    @Override
    public ValueCodec getValueCodec() {
        return this.config.valueCodec;
    }

    @Override
    public RemoteCacheConfig getConfig() {
        return config;
    }

}
